﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;

namespace AirLinq.Providers.Sql {
	public abstract class SqlVisitor {
		protected virtual SqlNode Visit(SqlNode sql) {
			if (sql == null) {
				return null;
			}
			switch (sql.NodeType) {
				case SqlNodeType.Add:
				case SqlNodeType.Sub:
				case SqlNodeType.Mul:
				case SqlNodeType.Div:
				case SqlNodeType.Mod:
				case SqlNodeType.BitAnd:
				case SqlNodeType.BitXor:
				case SqlNodeType.BitOr:
					return VisitBinaryScalarExpression((SqlBinaryScalarExpression)sql);
				case SqlNodeType.BitNot:
				case SqlNodeType.Positive:
				case SqlNodeType.Negative:
					return VisitUnaryScalarOperaion((SqlUnaryScalarOperaion)sql);
				case SqlNodeType.EQ:
				case SqlNodeType.NotEQ:
				case SqlNodeType.GT:
				case SqlNodeType.GE:
				case SqlNodeType.LT:
				case SqlNodeType.LE:
					return VisitComparision((SqlComparision)sql);
				case SqlNodeType.Union:
				case SqlNodeType.UnionAll:
				case SqlNodeType.Except:
				case SqlNodeType.Intersect:
					return VisitQueryExpression((SqlQueryExpression)sql);
				case SqlNodeType.Not:
					return VisitNot((SqlNot)sql);
				case SqlNodeType.And:
				case SqlNodeType.Or:
					return VisitBinaryLogicalExpression((SqlBinaryLogicalExpression)sql);
				case SqlNodeType.Between:
					return VisitBetween((SqlBetween)sql);
				case SqlNodeType.InSubquery:
					return VisitInSubquery((SqlInSubquery)sql);
				case SqlNodeType.InList:
					return VisitInList((SqlInList)sql);
				case SqlNodeType.Like:
					return VisitLike((SqlLike)sql);
				case SqlNodeType.Exists:
					return VisitExists((SqlExists)sql);
				case SqlNodeType.IsNull:
					return VisitIsNull((SqlIsNull)sql);
				case SqlNodeType.Subquery:
					return VisitSubquery((SqlSubquery)sql);
				case SqlNodeType.QuerySpecification:
					return VisitQuerySpecification((SqlQuerySpecification)sql);
				case SqlNodeType.SelectItemRef:
					return VisitSelectItemRef((SqlSelectItemRef)sql);
				/*
				case SqlNodeType.SelectItem:
					return VisitSelectItem((SqlSelectItem)sql);
				 */
				case SqlNodeType.Table:
					return VisitTable((SqlTable)sql);
				case SqlNodeType.SelectAll:
					return VisitSelectAll((SqlSelectAll)sql);
				case SqlNodeType.Column:
					return VisitColumn((SqlColumn)sql);
				case SqlNodeType.SelectCollection:
					return VisitSelectCollection((SqlSelectCollection)sql);
				case SqlNodeType.Join:
					return VisitJoin((SqlJoin)sql);
				case SqlNodeType.Select:
					return VisitSelect((SqlSelect)sql);
				case SqlNodeType.Group:
					return VisitGroup((SqlGroup)sql);
				case SqlNodeType.Parameter:
					return VisitParameter((SqlParameter)sql);
				default: throw new NotSupportedException("unknown SqlNodeType");
			}
		}

		protected virtual SqlSelectItemRef VisitSelectItemRef(SqlSelectItemRef sql) {
			var expr = (SqlExpression)Visit(sql.SelectItem.Expression);
			if (expr != sql.SelectItem.Expression) {
				//TODO thi is not implemented yet
				return new SqlSelectItemRef(new SelectItem(expr, sql.SelectItem.ColumnAlias),null);
			}
			return sql;
		}
		protected virtual SqlTable VisitTable(SqlTable sql) {
			return sql;
		}
		protected virtual SqlParameter VisitParameter(SqlParameter sql) {
			return sql;
		}
		protected virtual SqlQuerySpecification VisitQuerySpecification(SqlQuerySpecification sql) {
			var columns=(SqlSelectCollectionBase)Visit(sql.Columns);
			var source = (SqlSource)Visit(sql.TableSource);
			var where = (SqlLogicalExpression)Visit(sql.WhereClause);
			var group = (SqlGroup)Visit(sql.GroupClause);
			if (columns != sql.Columns
				|| source != sql.TableSource
				|| where != sql.WhereClause
				|| group != sql.GroupClause) {
				return new SqlQuerySpecification(columns, source, where, group);
			}
			return sql;
		}
		protected virtual SqlQueryExpression VisitQueryExpression(SqlQueryExpression sql) {
			var top = (SqlQueryExpressionBase)Visit(sql.Top);
			var bottom = (SqlQueryExpressionBase)Visit(sql.Bottom);
			if (top != sql.Top
				|| bottom != sql.Bottom) {
				return new SqlQueryExpression(sql.NodeType, top, bottom);
			}
			return sql;
		}
		protected virtual SqlColumn VisitColumn(SqlColumn sql) {
			return sql;
		}
		protected virtual SelectItem VisitSelectItem(SelectItem sql) {
			var expression = (SqlExpression)Visit(sql.Expression);
			if (expression != sql.Expression) {
				return new SelectItem(expression, sql.ColumnAlias);
			}
			return sql;
		}
		protected virtual SqlSelectAll VisitSelectAll(SqlSelectAll sql) {
			return sql;
		}
		protected virtual SqlJoin VisitJoin(SqlJoin sql) {
			var left = (SqlSource)Visit(sql.Left);
			var right = (SqlSource)Visit(sql.Right);
			var condition=(SqlLogicalExpression)Visit(sql.Condition);
			if (left!=sql.Left
				|| right != sql.Right
				|| condition!=sql.Condition){
				return new SqlJoin(left,right, sql.JoinType, condition);
			}
			return sql;
		}
		protected virtual SqlSelect VisitSelect(SqlSelect sql) {
			var query = (SqlQueryExpressionBase)Visit(sql.QueryExpression);
			var orderBy = VisitExpressionList(sql.OrderBy);
			if (query != sql.QueryExpression
				|| orderBy != sql.OrderBy) {
				return new SqlSelect(query, orderBy);
			}
			return sql;
		}
		protected virtual ReadOnlyCollection<SqlExpression> VisitExpressionList(ReadOnlyCollection<SqlExpression> source) {
			List<SqlExpression> list = null;
			var count = source.Count;
			for (int i = 0; i < count; i++) {
				var item = (SqlExpression)Visit(source[i]);
				if (list != null) {
					list.Add(item);
				} else if (item != source[i]) {
					list = new List<SqlExpression>(count);
					for (var j = 0; j < i; j++) {
						list.Add(source[j]);
					}
					list.Add(item);
				}
			}
			if (list != null) {
				return list.AsReadOnly();
			}
			return source;
		}
		protected virtual SqlBinaryLogicalExpression VisitBinaryLogicalExpression(SqlBinaryLogicalExpression sql) {
			var left = (SqlLogicalExpression)Visit(sql.Left);
			var right = (SqlLogicalExpression)Visit(sql.Right);
			if (left != sql.Left
				|| right != sql.Right) {
				return new SqlBinaryLogicalExpression(left, sql.NodeType, right);
			}
			return sql;
		}
		protected virtual SqlComparision VisitComparision(SqlComparision sql) {
			var left = (SqlExpression)Visit(sql.Left);
			var right = (SqlExpression)Visit(sql.Right);
			if (left != sql.Left
				|| right != sql.Right) {
				return new SqlComparision(left, sql.NodeType, right);
			}
			return sql;
		}
		protected virtual SqlNot VisitNot(SqlNot sql) {
			var inner = (SqlLogicalExpression)Visit(sql.Inner);
			if (inner != sql.Inner) {
				return new SqlNot(inner);
			}
			return sql;
		}
		protected virtual SqlIsNull VisitIsNull(SqlIsNull sql) {
			var test = (SqlExpression)Visit(sql.Test);
			if (test != sql.Test) {
				return new SqlIsNull(test);
			}
			return sql;
		}
		protected virtual SqlLike VisitLike(SqlLike sql) {
			var match = (SqlExpression)Visit(sql.Match);
			var pattern = (SqlExpression)Visit(sql.Pattern);
			var escape = (SqlExpression)Visit(sql.Escape);
			if (match != sql.Match
				|| pattern != sql.Pattern
				|| escape != sql.Escape) {
				return new SqlLike(match, pattern, escape);
			}
			return sql;
		}
		protected virtual SqlBetween VisitBetween(SqlBetween sql) {
			var test = (SqlExpression)Visit(sql.Test);
			var begin = (SqlExpression)Visit(sql.Begin);
			var end = (SqlExpression)Visit(sql.End);
			if (test != sql.Test
				|| begin != sql.Begin
				|| end != sql.End) {
				return new SqlBetween(test, begin, end);
			}
			return sql;
		}
		protected virtual SqlInList VisitInList(SqlInList sql) {
			var test = (SqlExpression)Visit(sql.Test);
			var list = VisitExpressionList(sql.ExpressionList);
			if (test != sql.Test
				|| list != sql.ExpressionList) {
				return new SqlInList(test, list);
			}
			return sql;
		}
		protected virtual SqlInSubquery VisitInSubquery(SqlInSubquery sql) {
			var test = (SqlExpression)Visit(sql.Test);
			var subQuery = (SqlQueryExpressionBase)Visit(sql.SubQuery);
			if (test != sql.Test
				|| subQuery != sql.SubQuery) {
				return new SqlInSubquery(test, subQuery);
			}
			return sql;
		}
		protected virtual SqlExists VisitExists(SqlExists sql) {
			var subQuery = (SqlQueryExpressionBase)Visit(sql.SubQuery);
			if (subQuery != sql.SubQuery) {
				return new SqlExists(subQuery);
			}
			return sql;
		}
		protected virtual SqlSubqueryComparision VisitSubqueryComparision(SqlSubqueryComparision sql) {
			var test = (SqlExpression)Visit(sql.Test);
			var subQuery = (SqlQueryExpressionBase)Visit(sql.SubQuery);
			if (test != sql.Test
				|| subQuery != sql.SubQuery) {
				return new SqlSubqueryComparision(test, sql.NodeType, sql.ForAll, sql.SubQuery);
			}
			return sql;
		}
		protected virtual SqlSelectCollection VisitSelectCollection(SqlSelectCollection sql) {
			var selectList = VisitSelectItemList(sql.SelectList);
			if (selectList != sql.SelectList) {
				return new SqlSelectCollection(selectList);
			}
			return sql;
		}
		protected virtual ReadOnlyCollection<SelectItem> VisitSelectItemList(ReadOnlyCollection<SelectItem> source) {
			List<SelectItem> list = null;
			var count = source.Count;
			for (int i = 0; i < count; i++) {
				var item=source[i];
				var expression = (SqlExpression)Visit(item.Expression);
				if (list != null) {
					list.Add(new SelectItem(expression, item.ColumnAlias));
				} else if (expression != item.Expression) {
					list = new List<SelectItem>(count);
					for (var j = 0; j < i; j++) {
						list.Add(source[j]);
					}
					list.Add(new SelectItem(expression,item.ColumnAlias));
				}
			}
			if (list != null) {
				return list.AsReadOnly();
			}
			return source;
		}
		protected virtual SqlGroup VisitGroup(SqlGroup sql) {
			var group = VisitExpressionList(sql.GroupList);
			if (group != sql.GroupList) {
				return new SqlGroup(group);
			}
			return sql;
		}
		protected virtual SqlBinaryScalarExpression VisitBinaryScalarExpression(SqlBinaryScalarExpression sql) {
			var left = (SqlExpression)Visit(sql.Left);
			var right = (SqlExpression)Visit(sql.Right);
			if (left != sql.Left
				|| right != sql.Right) {
				return new SqlBinaryScalarExpression(left, sql.NodeType, right);
			}
			return sql;
		}
		protected virtual SqlUnaryScalarOperaion VisitUnaryScalarOperaion(SqlUnaryScalarOperaion sql) {
			var inner = (SqlExpression)Visit(sql.Inner);
			if (inner != sql.Inner) {
				return new SqlUnaryScalarOperaion(inner, sql.NodeType);
			}
			return sql;
		}
		protected virtual SqlSubquery VisitSubquery(SqlSubquery sql) {
			var query = (SqlQuerySpecification)sql.Query;
			if (query != sql.Query) {
				return new SqlSubquery(query);
			}
			return sql;
		}
	}
}
